package com.star.aop;

import com.star.annotation.FeignTarget;
import com.star.exception.MyException;
import com.star.redis.RedisService;
import com.star.utils.BaseConstants;
import com.star.utils.JackonUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * @Author chaoyang
 * @Description:
 * @Date 2019/9/7 0007
 * @Version V1.0
 **/
@Aspect
@Component
public class LogAspect {

    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    private ThreadLocal<Long> startTime = new ThreadLocal<Long>();

    @Autowired
    private RedisService redisService;


    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")// 定义注解类型的切点，只要方法上有该注解，都会匹配
    @Order(1)
    public void annotationPoint() {

    }


    // 定义前置通知
    @Before("annotationPoint()")
    public void before(JoinPoint joinPoint) {
        logger.info("方法执行前执行.....before");
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        logger.info("<====================================================================");
        logger.info("请求来源:  => " + request.getRemoteAddr());
        logger.info("请求URL: " + request.getRequestURL().toString());
        logger.info("响应方法: " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        logger.info("请求参数 : " + Arrays.toString(joinPoint.getArgs()));
        logger.info("---------------------------------------------------------------------");
        startTime.set(System.currentTimeMillis());
    }

    @Around("annotationPoint() ")// 需要匹配切点表达式，同时需要匹配参数
    public Object around(ProceedingJoinPoint pjp) {

        logger.info("方法环绕start....around.");
        final Object[] args = pjp.getArgs();
        final String declaringTypeName = pjp.getSignature().getDeclaringTypeName();
        final String name = pjp.getSignature().getName();
        String packageMethod = declaringTypeName + "." + name;
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        final String access_token = servletRequestAttributes.getRequest().getHeader("access_token");
        Boolean exists = null;
        try {
            exists = redisService.setIfAbsent(access_token + packageMethod, "", 500, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            logger.error("redis 异常{}", e.getMessage());
        }
        //获取方法，此处可将signature强转为MethodSignature
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
        for (Annotation annotation : declaredAnnotations) {
            //这里判断当前注解是否为Test.class
            if (annotation.annotationType().equals(FeignTarget.class)) {
                //校验该参数，验证一次退出该注解
                //TODO 校验参数
                Object result = null;
                try {
                    result = pjp.proceed();
                } catch (Throwable e) {
                  e.printStackTrace();
                }
                logger.info("方法环绕end.....around");
                return result;
            }
        }

        if (exists != null && !exists) {
            logger.error("重复请求 --------- access_token = " + access_token + "调用方法" + packageMethod );
            throw new MyException(BaseConstants.DUPLICATE_SUBMIT_ERROR, "重复请求");
        }
        Object result = null;
        try {
            result = pjp.proceed();
        } catch (Throwable e) {
            logger.error("方法环绕 异常{}", e.getMessage());
        }
        logger.info("方法环绕end.....around");
        return result;
    }

    @After("within(com.star.*.*Controller)")
    public void after() {
        logger.info("方法之后执行....after.");
    }

    @AfterReturning(pointcut = "annotationPoint()", returning = "rst")
    public void afterReturning(JoinPoint joinPoint, Object rst) {
        String methodName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        logger.info("{}, 耗时 {},（毫秒）", methodName, (System.currentTimeMillis() - startTime.get()));
        logger.info("返回数据: {}", rst);
    }

    @AfterThrowing(value = "within(com.star.*.*Controller)")
    public void afterThrowing() {

        logger.error("异常出现之后.....afterThrowing");
    }

}
